﻿using AutoMapper;
using Mes.Infrastructure.Data.BoundedContext.Extensions;
using Mes.Domain.BoundedContext;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using Mes.Application.Common;
using Mes.Application.Common.Resource;
using Mes.Application.DTO.Inputs.MasterData;
using Mes.Application.DTO.Results.MasterData;
using Mes.Domain.BoundedContext.Entities.MasterData;
using Mes.Infrastructure.Data.BoundedContext.Repositories.MasterData;

namespace Mes.Application.Services.MasterData
{
    public class MaterialService : IMaterialService
    {
        private readonly IMaterialRepository _materialRepository;
        private readonly IMapper _mapper;
        private readonly IMaterialClassRepository _materialClassRepository;
        private readonly IMaterialMaterialBOMLinkRepository _materialMaterialBOMLinkRepository;
        private readonly IMeasuringUnitRepository _measuringUnitRepository;
        private readonly IProductionUnitRepository _productionUnitRepository;
        private readonly IMaterialBOMRepository _materialBOMRepository;
        private readonly IMaterialBOMMaterialLinkRepository _materialBOMMaterialLinkRepository;

        public MaterialService(IMapper mapper, IMaterialRepository materialRepository, IMaterialClassRepository materialClassRepository, IMaterialMaterialBOMLinkRepository materialMaterialBOMLinkRepository, IMeasuringUnitRepository measuringUnitRepository, IProductionUnitRepository productionUnitRepository, IMaterialBOMRepository materialBOMRepository, IMaterialBOMMaterialLinkRepository materialBOMMaterialLinkRepository)
        {
            if (mapper == null || materialRepository == null || materialClassRepository == null || materialMaterialBOMLinkRepository == null
                || measuringUnitRepository == null || productionUnitRepository == null || materialBOMRepository == null || materialBOMMaterialLinkRepository == null)
            {
                throw new ArgumentNullException();
            }
            _mapper = mapper;
            _materialRepository = materialRepository;
            _materialClassRepository = materialClassRepository;
            _materialMaterialBOMLinkRepository = materialMaterialBOMLinkRepository;
            _measuringUnitRepository = measuringUnitRepository;
            _productionUnitRepository = productionUnitRepository;
            _materialBOMRepository = materialBOMRepository;
            _materialBOMMaterialLinkRepository = materialBOMMaterialLinkRepository;
        }

        /// <summary>
        /// 新增物料
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task AddMaterialAsync(AddMaterialInput input)
        {
            if (await _materialRepository.ExistsAsync(x => x.Code == input.Code || x.Name == input.Name))
            {
                throw new CustomException(Messages.MaterialDuplication);
            }
            var materialClass = await _materialClassRepository.FindAsync(x => x.Id == input.MaterialClassId && x.Name == input.MaterialClassName);
            if (materialClass == null)
            {
                throw new CustomException(Messages.MaterialClassNotFound);
            }
            if (!await _measuringUnitRepository.ExistsAsync(x => x.Id == input.MeasuringUnitId && x.UnitName == input.MeasuringUnitName))
            {
                throw new CustomException(Messages.MeasuringUnitNotFound);
            }
            if (input.ProdutionUnitId != null && input.ProdutionUnitId.Value != 0)
            {
                if (!await _productionUnitRepository.ExistsAsync(x => x.Id == input.ProdutionUnitId && x.ProductionUnitName == input.ProdutionUnitName))
                {
                    throw new CustomException(Messages.ProductionUnitNotFound);
                }
            }
            if (input.MaterialBOMIds.Distinct().Count() != input.MaterialBOMIds.Count)
            {
                throw new CustomException(Messages.MaterialBOMDuplication);
            }
            var bomCount = await _materialBOMRepository.CountAsync(x => input.MaterialBOMIds.Contains(x.Id));
            if (bomCount != input.MaterialBOMIds.Count)
            {
                throw new CustomException(Messages.SomeMaterialBOMNotFound);
            }

            var material = _mapper.Map<Material>(input);
            material.MaterialClassTypeId = materialClass.TypeId;
            material.MaterialClassTypeName = materialClass.TypeName;
            await _materialRepository.AddAsync(material);
            int status = await _materialRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
            if (input.MaterialBOMIds.Count > 0)
            {
                var links = new List<MaterialMaterialBOMLink>();
                foreach (var id in input.MaterialBOMIds)
                {
                    var link = new MaterialMaterialBOMLink
                    {
                        MaterialBOMId = id,
                        MaterialId = material.Id,
                    };
                    links.Add(link);
                }
                await _materialMaterialBOMLinkRepository.AddRangeAsync(links);
                status = await _materialMaterialBOMLinkRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
        }

        /// <summary>
        /// 更新物料
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task UpdateMaterialAsync(UpdateMaterialInput input)
        {
            var material = await _materialRepository.FindAsync(x => x.Id == input.Id);
            if (material == null)
            {
                throw new CustomException(Messages.MaterialNotFound);
            }
            var materialClass = await _materialClassRepository.FindAsync(x => x.Id == input.MaterialClassId && x.Name == input.MaterialClassName);
            if (materialClass == null)
            {
                throw new CustomException(Messages.MaterialClassNotFound);
            }
            if (!await _measuringUnitRepository.ExistsAsync(x => x.Id == input.MeasuringUnitId && x.UnitName == input.MeasuringUnitName))
            {
                throw new CustomException(Messages.MeasuringUnitNotFound);
            }
            if (input.ProdutionUnitId != null && input.ProdutionUnitId.Value != 0)
            {
                if (!await _productionUnitRepository.ExistsAsync(x => x.Id == input.ProdutionUnitId && x.ProductionUnitName == input.ProdutionUnitName))
                {
                    throw new CustomException(Messages.ProductionUnitNotFound);
                }
            }
            var bomCount = await _materialBOMRepository.CountAsync(x => input.MaterialBOMIds.Contains(x.Id));
            if (bomCount != input.MaterialBOMIds.Count)
            {
                throw new CustomException(Messages.SomeMaterialBOMNotFound);
            }
            material.MaterialClassId = input.MaterialClassId;
            material.MaterialClassName = input.MaterialClassName;
            material.MeasuringUnitId = input.MeasuringUnitId;
            material.MeasuringUnitName = input.MeasuringUnitName;
            material.ProdutionUnitId = input.ProdutionUnitId;
            material.ProdutionUnitName = input.ProdutionUnitName;
            material.PrecisionPlace = input.PrecisionPlace;
            material.Remark = input.Remark;
            material.Specification = input.Specification;
            material.Updatetime = DateTime.Now;
            material.MaterialClassTypeId = materialClass.TypeId;
            material.MaterialClassTypeName = materialClass.TypeName;
            await _materialMaterialBOMLinkRepository.DeleteAsync(x => x.MaterialId == input.Id);
            if (input.MaterialBOMIds?.Count > 0)
            {
                var links = new List<MaterialMaterialBOMLink>();
                foreach (var id in input.MaterialBOMIds)
                {
                    var link = new MaterialMaterialBOMLink
                    {
                        MaterialBOMId = id,
                        MaterialId = material.Id,
                    };
                    links.Add(link);
                }
                await _materialMaterialBOMLinkRepository.AddRangeAsync(links);
            }
            int status = await _materialRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }

        /// <summary>
        /// 删除物料
        /// </summary>
        /// <param name="ids"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task DeleteMaterialsAsync(IList<int> ids)
        {
            if (await _materialBOMMaterialLinkRepository.ExistsAsync(x => ids.Contains(x.MaterialId)))
            {
                throw new CustomException(Messages.BOMAlreadyBindingMaterial);
            }

            await _materialRepository.DeleteAsync(x => ids.Contains(x.Id));
            await _materialMaterialBOMLinkRepository.DeleteAsync(x => ids.Contains(x.MaterialId));
            int status = await _materialRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }

        /// <summary>
        /// 分页查询物料
        /// </summary>
        /// <param name="input"></param>
        /// <returns>PagedItemsData<MaterialsResults></returns>
        public async Task<PagedItemsData<MaterialsResults>> GetMaterialsAsync(GetMaterialsInput input)
        {
            Expression<Func<Material, bool>> filter = x => true;
            if (!string.IsNullOrEmpty(input.KeyWord))
            {
                filter = filter.And(x => x.Name.Contains(input.KeyWord) || x.Code.Contains(input.KeyWord));
            }
            if (!string.IsNullOrEmpty(input.MaterialClassTypeName))
            {
                filter = filter.And(x => x.MaterialClassTypeName == input.MaterialClassTypeName);
            }
            var data = await _materialRepository.Query().Where(filter).OrderByDescending(x => x.CreateTime).ToPagedAsync(input.PagedInput);
            var result = _mapper.Map<PagedItemsData<MaterialsResults>>(data);
            var materialIds = result.Items.Select(x => x.Id).ToList();
            var links = await _materialMaterialBOMLinkRepository.Query().Where(x => materialIds.Contains(x.MaterialId)).ToListAsync();
            var materialBOMIds = links.Select(x => x.MaterialBOMId).ToList();
            if (materialBOMIds.Count > 0)
            {
                var materialBOMS = await _materialBOMRepository.Query().Where(x => materialBOMIds.Contains(x.Id)).ToListAsync();
                foreach (var item in result.Items)
                {
                    var currentLinks = links.Where(x => x.MaterialId == item.Id).ToList();
                    var currentBOMIds = currentLinks.Select(x => x.MaterialBOMId).ToList();
                    var currentBOMs = materialBOMS.Where(x => currentBOMIds.Contains(x.Id)).ToList();
                    item.MaterialBOMs = _mapper.Map<List<IdNameResult>>(currentBOMs);
                }
            }
            return result;
        }

        /// <summary>
        /// 查询物料
        /// </summary>
        /// <param name="id"></param>
        /// <returns>MaterialResults</returns>
        /// <exception cref="CustomException"></exception>
        public async Task<MaterialResults> GetMaterialAsync(int id)
        {
            var material = await _materialRepository.FindAsync(x => x.Id == id);
            if (material == null)
            {
                throw new CustomException(Messages.MaterialNotFound);
            }
            var result = _mapper.Map<MaterialResults>(material);
            var links1 = await _materialMaterialBOMLinkRepository.Query().Where(x => x.MaterialId == id).ToListAsync();
            var MaterialBOMIds = links1.Select(x => x.MaterialBOMId).ToList();
            if (MaterialBOMIds.Count > 0)
            {
                var materialBOMs = await _materialBOMRepository.Query().Where(x => MaterialBOMIds.Contains(x.Id)).OrderByDescending(x => x.CreateTime).ToListAsync();
                result.MaterialBOMs = _mapper.Map<List<MaterialBOMResult>>(materialBOMs);
                if (materialBOMs.Count > 0)
                {
                    var links2 = await _materialBOMMaterialLinkRepository.Query().Where(x => MaterialBOMIds.Contains(x.MaterialBOMId)).ToListAsync();
                    var materialIds = links2.Select(x => x.MaterialId).ToList();
                    if (materialIds.Count > 0)
                    {
                        var materials = await _materialRepository.Query().Where(x => materialIds.Contains(x.Id)).ToListAsync();
                        foreach (var item in result.MaterialBOMs)
                        {
                            var currentLinks = links2.Where(x => x.MaterialBOMId == item.Id).ToList();
                            var currentMaterialIds = currentLinks.Select(x => x.MaterialId).ToList();
                            var currentMaterials = materials.Where(x => currentMaterialIds.Contains(x.Id)).ToList();
                            item.Materials = _mapper.Map<List<MaterialResult>>(currentMaterials);
                            foreach (var consumableMaterial in item.Materials)
                            {
                                consumableMaterial.MaterialNumber = currentLinks.FirstOrDefault(x => x.MaterialId == consumableMaterial.Id && x.MaterialBOMId == item.Id)?.MaterialNumber ?? 0;
                            }
                        }
                    }
                }
            }
            return result;
        }
    }
}
